using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Reflection;

namespace Polymod
{
    internal class ExpressionHelper
    {
        private class SimpleInvoker<T, R> : IInvoker
        {
            private readonly object _synclock = new object();
            private volatile Func<T, R> _getter = null;
            private volatile Action<T, R> _setter = null;
            private string _name;
            private bool _isReadonly;

            public SimpleInvoker(string name, bool isReadonly)
            {
                _name = name;
                _isReadonly = isReadonly;
            }

            public R GetValue(T target)
            {
                if (_getter == null)
                {
                    lock (_synclock)
                    {
                        if (_getter == null)
                        {
                            var p = Expression.Parameter(typeof(T), "p");
                            var getP = Expression.PropertyOrField(p, _name);
                            var lambda = Expression.Lambda(getP, p);
                            _getter = (Func<T, R>)lambda.Compile();
                        }
                    }
                }
                return _getter(target);
            }

            public void SetValue(T target, R value)
            {
                if (_isReadonly) throw new InvalidOperationException(string.Format("Invoker for '{0}' is readonly.", _name));
                if (_setter == null)
                {
                    lock (_synclock)
                    {
                        if (_setter == null)
                        {
                            var p = Expression.Parameter(typeof(T), "p");
                            var v = Expression.Parameter(typeof(R), "v");
                            var property = Expression.PropertyOrField(p, _name);
                            var assign = Expression.Assign(property, v);
                            var lambda = Expression.Lambda<Action<T, R>>(assign, p, v);
                            _setter = (Action<T, R>)lambda.Compile();
                        }
                    }
                }
                _setter(target, value);
            }

            object IInvoker.GetValue(IModel target)
            {
                return GetValue((T)target.GetValue());
            }

            void IInvoker.SetValue(IModel target, object value)
            {
                SetValue((T)target.GetValue(), (R)value);
            }


            public Type ReturnType
            {
                get { return typeof(R); }
            }

            /// <summary>
            /// Gets IsReadonly
            /// </summary>
            public bool IsReadonly
            {
                get { return _isReadonly; }
            }

            /// <summary>
            /// Gets Name
            /// </summary>
            public string Name
            {
                get { return _name; }
            }

            IInvoker IInvoker.Clone()
            {
                return this.Clone();
            }

            public IInvoker Clone()
            {
                var result = new SimpleInvoker<T, R>(_name, _isReadonly);
                result._getter = this._getter;
                result._setter = this._setter;
                return result;
            }
        }

        public static IInvoker CreateInvoker(PropertyInfo propertyInfo)
        {
            if (propertyInfo == null) throw new System.ArgumentNullException("propertyInfo");
            return CreateInvoker(propertyInfo.ReflectedType, propertyInfo.PropertyType, propertyInfo.Name, !propertyInfo.CanWrite);
        }

        public static IInvoker CreateInvoker(FieldInfo fieldInfo)
        {
            if (fieldInfo == null) throw new System.ArgumentNullException("propertyInfo");
            return CreateInvoker(fieldInfo.ReflectedType, fieldInfo.FieldType, fieldInfo.Name, fieldInfo.IsInitOnly);
        }

        private static IInvoker CreateInvoker(Type t, Type r, string name, bool isReadonly)
        {
            var type = typeof(SimpleInvoker<,>).MakeGenericType(t, r);
            var result = (IInvoker)Activator.CreateInstance(type, name, isReadonly);
            return result;
        }
    }
}
